#include "types.hpp"
#include "vga.hpp"

namespace vga{
    word * const videoram = (word *)0x000b8000;

    int cursor_x = 0;
    int cursor_y = 0;

    const int width = 80;
    const int height = 25;

    const int tab_width = 4;

    byte color = 0x07;

    void putchar(char x){
        switch(x){
            case '\n':
                cursor_y++;
                if (cursor_y >= height) scroll(1);
            case '\r':
                cursor_x = 0;
                break;
            case '\t':
                do{
                    putchar(' ');
                }while(cursor_x % tab_width);
                break;
            default:
                if (cursor_x >= width) putchar('\n');
                videoram[cursor_x++ + cursor_y * width] = (color << 8) + (unsigned char)x;
                break;
        }
    }

    void home(){
        cursor_x = cursor_y = 0;
    }

    void cls(){
        for(int y = 0; y < height; y++){
            clearline(y);
        }
        home();
    }

    void clearline(int line){
        for(int x = 0; x < width; x++){
            videoram[x + line * width] = (color << 8) + ' ';
        }
    }

    void putstring(const char *s){
        while(*s != '\0') putchar(*(s++));
    }

    void puts(const char *s){
        putstring(s);
        putchar('\n');
    }

    void scroll(int lines){
        int i;
        for(i = 0; i < height-lines; i++){
            for(int x = 0; x < width; x++){
                videoram[x + i * width] = videoram[x + (i+lines) * width];
            }
        }
        for(; i < height; i++){
            clearline(i);
        }
        cursor_y -= lines;
        if (cursor_y < 0) cursor_y = 0;
    }

    void printf(const char * format, ...){
        struct x{
            static void itoa(char *buf, char base, int d){
                char * p = buf;
                char letters = 'a';
                unsigned long value = d;
                int divisor = 10;
                int minlength = 0;

                if (base == 'd' && d < 0){
                    *p++ = '-';
                    buf++;
                    value = -d;
                } else if (base == 'x') {
                    divisor = 16;
                } else if (base == 'X') {
                    minlength = 8;
                    divisor = 16;
                    letters = 'A';
                } else if (base == 'p') {
                    *p++ = '0';
                    *p++ = 'x';
                    buf += 2;
                    divisor = 16;
                    letters = 'A';
                    minlength = 8;
                }

                do{
                    int remainder = value % divisor;
                    *p++ = (remainder < 10) ? remainder + '0' : remainder + letters - 10;
                    minlength--;
                }while(value /= divisor);

                while(minlength-- > 0) *p++ = '0';

                *p = '\0';

                char * p1 = buf;
                char * p2 = p - 1;
                while(p1 < p2){
                    char tmp = *p1;
                    *p1++ = *p2;
                    *p2-- = tmp;
                }
            }
        };

        void * * arg = (void * *)&format;
        arg++;
        for(char c = *format++; c != '\0'; c = *format++){
            if (c == '%'){
                c = *format++;
                switch (c){
                    case 'd':
                    case 'u':
                    case 'x':
                    case 'X':
                    case 'p':{
                        char buf[20];
                        x::itoa(buf, c, *((int *)arg++));
                        putstring(buf);
                        break;
                    }

                    case 's':{
                        const char * p = (const char *) *arg++;
                        if (p == NULL) p = "(null)";
                        putstring(p);
                        break;
                    }

                    case 'c':
                        putchar(*((char *)arg++));
                        break;

                    default:
                        putchar(c);
                        break;
                }
            } else if (c == '#') {
                char b = *format++;
                char f = *format++;
                b = (b=='#')?0:(b >= 'A')?(b >= 'a')?b-'a'+10:b-'A'+10:b-'0';
                f = (f=='#')?7:(f >= 'A')?(f >= 'a')?f-'a'+10:f-'A'+10:f-'0';
                vga::color = (b << 4) + f;
            } else {
                putchar(c);
            }
        }
    }

}

